{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9279763f",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "圆柱绕流势流场可视化程序\n",
    "\n",
    "功能特性：\n",
    "- 实时交互式参数调整（圆柱半径、来流速度）\n",
    "- 基于势流理论的流场数值计算\n",
    "- Numba加速核心算法\n",
    "- PyQt5构建图形界面\n",
    "- Matplotlib实现专业可视化\n",
    "- 输入验证与物理验证机制\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "241627c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ================== 基础库导入 ==================\n",
    "import numpy as np         # 数值计算核心库，用于矩阵运算和网格生成\n",
    "import numba as nb         # 即时编译器，用于加速数值计算核心算法"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "768f409d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ================== GUI框架相关库 ==================\n",
    "from PyQt5.QtWidgets import (\n",
    "    QApplication,   # 应用管理类（管理事件循环等）\n",
    "    QMainWindow,    # 主窗口基类\n",
    "    QVBoxLayout,    # 垂直布局管理器\n",
    "    QWidget,        # 基础控件容器\n",
    "    QSlider,        # 滑动条控件\n",
    "    QLineEdit,      # 文本输入框\n",
    "    QLabel          # 文本标签\n",
    ")\n",
    "from PyQt5.QtCore import Qt        # Qt核心功能（包含方向常量等）\n",
    "from PyQt5.QtGui import QValidator # 输入验证基类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1bb02293",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "# ================== 可视化库导入 ==================\n",
    "from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas  # Qt5后端画布\n",
    "from matplotlib.figure import Figure  # 图表容器类\n",
    "import matplotlib.pyplot as plt       # 绘图接口，提供流线图、等势线等可视化功能"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3c0ca05d",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "# ================== 物理参数和计算核心 ==================\n",
    "nu = 1.5e-5  # 空气的运动粘性系数 (m²/s)，标准大气条件15℃时的典型值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e1290a52",
   "metadata": {
    "lines_to_next_cell": 1
   },
   "outputs": [],
   "source": [
    "@nb.njit(parallel=True)\n",
    "def compute_velocity(X, Y, U, a):\n",
    "    \"\"\"\n",
    "    使用Numba加速的势流速度场计算（圆柱绕流解析解）\n",
    "    \n",
    "    参数:\n",
    "        X, Y (ndarray): 网格坐标矩阵，形状相同的二维数组\n",
    "        U (float): 来流速度，单位m/s\n",
    "        a (float): 圆柱半径，单位m\n",
    "        \n",
    "    返回:\n",
    "        Vx, Vy (ndarray): x和y方向的速度分量矩阵，与输入网格同形\n",
    "        \n",
    "    算法:\n",
    "        基于势流理论解析解推导速度场：\n",
    "        1. 复势函数：F(z) = U(z + a²/z)\n",
    "        2. 复速度：V = dF/dz = U(1 - a²/z²)\n",
    "        3. 分解实部虚部得到各点速度分量\n",
    "    \"\"\"\n",
    "    Vx = np.zeros_like(X)\n",
    "    Vy = np.zeros_like(Y)\n",
    "    a_sq = a**2  # 圆柱半径平方提前计算\n",
    "    \n",
    "    # 并行遍历网格点（Numba自动优化循环）\n",
    "    for i in nb.prange(X.shape[0]):\n",
    "        for j in nb.prange(X.shape[1]):\n",
    "            x = X[i, j]\n",
    "            y = Y[i, j]\n",
    "            r_sq = x**2 + y**2  # 计算到原点的距离平方\n",
    "            \n",
    "            # 排除原点附近和圆柱内部的点（避免奇异值）\n",
    "            if r_sq < 1e-6 or r_sq <= a_sq:\n",
    "                continue  # 保持速度为零\n",
    "                \n",
    "            z_sq_inv = 1 / r_sq  # 1/(x²+y²)\n",
    "            # 分解复速度的实部和虚部\n",
    "            real_part = 1 - a_sq*(x**2 - y**2)*z_sq_inv**2\n",
    "            imag_part = 2*a_sq*x*y*z_sq_inv**2\n",
    "            \n",
    "            Vx[i, j] = U * real_part  # x方向速度分量\n",
    "            Vy[i, j] = -U * imag_part  # y方向速度分量（负号保证速度方向正确）\n",
    "\n",
    "    return Vx, Vy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2b78cfe0",
   "metadata": {},
   "outputs": [],
   "source": [
    "def calculate_stream_function(X, Y, U, a):\n",
    "    \"\"\"\n",
    "    计算势流流函数（圆柱绕流解析解）\n",
    "    \n",
    "    参数:\n",
    "        X, Y (ndarray): 网格坐标矩阵，形状相同的二维数组\n",
    "        U (float): 来流速度，单位m/s\n",
    "        a (float): 圆柱半径，单位m\n",
    "        \n",
    "    返回:\n",
    "        psi (ndarray): 流函数值矩阵，与输入网格同形\n",
    "        \n",
    "    注意:\n",
    "        当r=0时添加微小值(1e-10)避免除以零错误，保持计算稳定性\n",
    "        流函数公式：ψ = U*y*(1 - a²/(x²+y²))\n",
    "    \"\"\"\n",
    "    r_sq = X**2 + Y**2\n",
    "    safe_r_sq = np.where(r_sq != 0, r_sq, 1e-10)  # 处理原点附近的奇异点\n",
    "    return U * Y * (1 - a**2 / safe_r_sq)  # 势流理论标准解"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5724b8ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ================== 输入验证器 ==================\n",
    "class FloatValidator(QValidator):\n",
    "    \"\"\"\n",
    "    自定义浮点数验证器（限制输入范围1.0~10.0）\n",
    "    \n",
    "    特性:\n",
    "        - 实时验证输入有效性（绿色/红色边框反馈）\n",
    "        - 自动修正超出范围的数值到边界值\n",
    "        - 拒绝非数字输入\n",
    "        - 支持中间状态（允许空输入）\n",
    "    \"\"\"\n",
    "    def __init__(self, min_val, max_val, parent=None):\n",
    "        super().__init__(parent)\n",
    "        self.min_val = min_val  # 最小值边界\n",
    "        self.max_val = max_val  # 最大值边界\n",
    "\n",
    "    def validate(self, input_str, pos):\n",
    "        \"\"\"实时验证输入内容\"\"\"\n",
    "        if not input_str:\n",
    "            # 允许空输入作为中间状态（方便用户编辑）\n",
    "            return (QValidator.Intermediate, input_str, pos)\n",
    "        \n",
    "        try:\n",
    "            value = float(input_str)\n",
    "            if self.min_val <= value <= self.max_val:\n",
    "                return (QValidator.Acceptable, input_str, pos)\n",
    "            return (QValidator.Invalid, input_str, pos)\n",
    "        except ValueError:\n",
    "            return (QValidator.Invalid, input_str, pos)\n",
    "\n",
    "    def fixup(self, input_str):\n",
    "        \"\"\"自动修正非法输入为最近边界值\"\"\"\n",
    "        try:\n",
    "            value = float(input_str)\n",
    "            value = np.clip(value, self.min_val, self.max_val)\n",
    "            return f\"{value:.1f}\"  # 格式化为1位小数\n",
    "        except:\n",
    "            return str(self.min_val)  # 默认返回最小值"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ea928045",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ================== 主界面类 ==================\n",
    "class MainWindow(QMainWindow):\n",
    "    \"\"\"\n",
    "    主窗口类，负责GUI布局和可视化更新\n",
    "    \n",
    "    功能特性:\n",
    "        - 实时交互式参数调整（滑动条+输入框）\n",
    "        - 流场动态可视化（流线+等势线）\n",
    "        - 雷诺数自动计算与显示\n",
    "        - 流线闭合性物理验证\n",
    "        - 界面样式美化\n",
    "    \"\"\"\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        # 初始化物理参数\n",
    "        self.a = 1.0  # 圆柱半径 (m)，对应滑动条中间位置\n",
    "        self.U = 5.0  # 来流速度 (m/s)，默认值\n",
    "        \n",
    "        self.init_ui()      # 初始化界面组件\n",
    "        self.update_plot()  # 首次绘制\n",
    "\n",
    "    def init_ui(self):\n",
    "        \"\"\"初始化用户界面组件和布局\"\"\"\n",
    "        self.setWindowTitle(\"流场可视化系统 v2.0\")\n",
    "        central_widget = QWidget()\n",
    "        self.setCentralWidget(central_widget)\n",
    "        layout = QVBoxLayout(central_widget)\n",
    "\n",
    "        # 圆柱半径控制滑动条（范围0.5~2.0m，精度0.1m）\n",
    "        self.slider_a = QSlider(Qt.Horizontal)\n",
    "        self.slider_a.setRange(5, 20)  # 内部使用整数避免浮点误差，实际值需除以10\n",
    "        self.slider_a.setValue(10)     # 初始值对应1.0m\n",
    "        self.slider_a.valueChanged.connect(self.update_a)\n",
    "        layout.addWidget(QLabel(\"圆柱半径 (m):\"))\n",
    "        layout.addWidget(self.slider_a)\n",
    "\n",
    "        # 流速输入框（带范围验证）\n",
    "        self.lineEdit_U = QLineEdit(str(self.U))\n",
    "        self.lineEdit_U.setValidator(FloatValidator(1.0, 10.0, self))\n",
    "        self.lineEdit_U.editingFinished.connect(self.update_U)\n",
    "        layout.addWidget(QLabel(\"流速 U (m/s):\"))\n",
    "        layout.addWidget(self.lineEdit_U)\n",
    "\n",
    "        # 状态显示栏（雷诺数和验证结果）\n",
    "        self.status_label = QLabel()\n",
    "        layout.addWidget(self.status_label)\n",
    "\n",
    "        # Matplotlib绘图区域设置\n",
    "        self.figure = Figure(figsize=(8, 6), facecolor='black')  # 黑色背景\n",
    "        self.canvas = FigureCanvas(self.figure)\n",
    "        layout.addWidget(self.canvas)\n",
    "\n",
    "        # 界面样式美化\n",
    "        self.setStyleSheet(\"\"\"\n",
    "            QMainWindow { background: #333; }\n",
    "            QLabel { color: #EEE; font: 10pt Arial; }\n",
    "            QSlider::groove:horizontal {\n",
    "                background: #555;\n",
    "                height: 8px;\n",
    "                border-radius: 4px;\n",
    "            }\n",
    "            QSlider::handle:horizontal {\n",
    "                background: #FF8C00;\n",
    "                width: 16px;\n",
    "                margin: -4px 0;\n",
    "                border-radius: 8px;\n",
    "            }\n",
    "            QLineEdit {\n",
    "                border: 2px solid #888;\n",
    "                border-radius: 4px;\n",
    "                padding: 2px;\n",
    "                background: #444;\n",
    "                color: #FFF;\n",
    "            }\n",
    "            QLineEdit:valid { border-color: #0F0; }\n",
    "            QLineEdit:invalid { border-color: #F00; }\n",
    "        \"\"\")\n",
    "        self.resize(1000, 800)  # 初始窗口尺寸\n",
    "\n",
    "    def update_a(self):\n",
    "        \"\"\"处理圆柱半径变化事件（滑动条回调）\"\"\"\n",
    "        self.a = self.slider_a.value() / 10.0  # 将滑动条整数转换为浮点值\n",
    "        self.update_plot()\n",
    "\n",
    "    def update_U(self):\n",
    "        \"\"\"处理流速输入变化事件（文本框回调）\"\"\"\n",
    "        try:\n",
    "            self.U = float(self.lineEdit_U.text())\n",
    "            self.U = np.clip(self.U, 1.0, 10.0)  # 强制限制在有效范围\n",
    "            self.lineEdit_U.setText(f\"{self.U:.1f}\")  # 格式化显示\n",
    "        except ValueError:\n",
    "            pass  # 忽略无效输入\n",
    "        self.update_plot()\n",
    "\n",
    "    def update_plot(self):\n",
    "        \"\"\"更新流场可视化图形（核心绘图方法）\"\"\"\n",
    "        # 生成计算网格（范围：±5m，分辨率400×400）\n",
    "        X, Y = np.meshgrid(np.linspace(-5, 5, 400),\n",
    "                          np.linspace(-5, 5, 400))\n",
    "        \n",
    "        # 核心计算（Numba加速）\n",
    "        Vx, Vy = compute_velocity(X, Y, self.U, self.a)\n",
    "        psi = calculate_stream_function(X, Y, self.U, self.a)\n",
    "\n",
    "        # 清除旧图形\n",
    "        self.figure.clf()\n",
    "        ax = self.figure.add_subplot(111, facecolor='black')  # 黑色背景坐标系\n",
    "\n",
    "        # 绘制流线图（白色，带速度箭头）\n",
    "        ax.streamplot(X, Y, Vx, Vy, \n",
    "                     color='white', linewidth=0.8,\n",
    "                     density=1.5, arrowsize=0.8)\n",
    "\n",
    "        # 绘制等势线（黄色虚线，15个层级）\n",
    "        ax.contour(X, Y, psi, levels=15,\n",
    "                  colors='yellow', linestyles='--',\n",
    "                  linewidths=0.6, alpha=0.8)\n",
    "\n",
    "        # 添加圆柱体表示（半透明红色圆形）\n",
    "        ax.add_patch(plt.Circle((0, 0), self.a,\n",
    "                              color='red', alpha=0.3))\n",
    "\n",
    "        # 标注驻点位置（圆柱表面x轴两端）\n",
    "        ax.plot([self.a, -self.a], [0, 0], 'ro', markersize=5)\n",
    "\n",
    "        # 坐标轴设置\n",
    "        ax.set(xlim=(-5, 5), ylim=(-5, 5),  # 固定显示范围\n",
    "             xticks=[], yticks=[],          # 隐藏坐标刻度\n",
    "             aspect='equal')               # 保持纵横比一致\n",
    "\n",
    "        # 更新状态信息\n",
    "        Re = 2 * self.U * self.a / nu  # 雷诺数计算公式（特征长度为2a）\n",
    "        valid = validate_streamlines(self.a, self.U)\n",
    "        status = (f\"雷诺数 Re = {Re:.1e} | \"\n",
    "                 f\"状态: {'✔ 验证通过' if valid else '✖ 验证失败'}\")\n",
    "        self.status_label.setText(status)\n",
    "        # 根据验证结果设置颜色反馈\n",
    "        self.status_label.setStyleSheet(\n",
    "            \"color: #0F0;\" if valid else \"color: #F00;\")  \n",
    "\n",
    "        self.canvas.draw()  # 触发画布重绘"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4f8b5735",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ================== 物理验证 ==================\n",
    "def validate_streamlines(a, U):\n",
    "    \"\"\"\n",
    "    流线闭合性验证（圆柱表面应为流线）\n",
    "    \n",
    "    参数:\n",
    "        a (float): 圆柱半径，单位m\n",
    "        U (float): 来流速度，单位m/s\n",
    "    \n",
    "    返回:\n",
    "        bool: 是否通过验证（圆柱表面流函数标准差小于阈值）\n",
    "    \n",
    "    方法:\n",
    "        1. 在圆柱表面均匀采样10个点\n",
    "        2. 计算各点的流函数值\n",
    "        3. 验证标准差是否小于阈值（U*a的1%）\n",
    "    \"\"\"\n",
    "    theta = np.linspace(0, 2*np.pi, 10)  # 在圆周上均匀采样\n",
    "    x = a * np.cos(theta)\n",
    "    y = a * np.sin(theta)\n",
    "    psi_values = calculate_stream_function(x, y, U, a)\n",
    "    return np.std(psi_values) < 0.01 * U * a  # 允许1%的误差"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4c10fb1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ================== 预编译和启动 ==================\n",
    "if __name__ == '__main__':\n",
    "    # Numba预编译（避免首次运行时的编译延迟）\n",
    "    X_dummy = np.ones((2,2), dtype=np.float64)\n",
    "    compute_velocity(X_dummy, X_dummy, 1.0, 0.5)  # 用虚拟数据触发编译\n",
    "    \n",
    "    # 启动Qt应用\n",
    "    app = QApplication([])\n",
    "    window = MainWindow()\n",
    "    window.show()\n",
    "    app.exec_()"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "encoding": "# -*- coding: utf-8 -*-",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
